# -*- coding: utf-8 -*-
# Project : flacon
# File : trackwiev.py
# (c) Alexander Sokolov sokoloff.a@gmail.com
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 3
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.


import os
import re
import copy

from PyQt4 import QtGui, QtCore
from PyQt4.QtCore import *
from PyQt4.QtGui import *

from settings import *
from flaconglobal import *
from disc import *
from trackset import *
from track import *

import pyqticonloader

# Compatible fix
try:
    QStyledItemDelegate
except NameError:
     QStyledItemDelegate = QItemDelegate


class TrackViewDelegate(QStyledItemDelegate):
    MARGIN           = 6
    TOP_PADDING      = 16
    BOTTOM_PADDING   = 2
    IMG_HEIGHT       = 60
    MARK_HEIGHT      = 32
    LINE_MARK_HEIGHT = 22

    CLICK_NONE      = 0
    CLICK_TRACKBTN  = 1
    CLICK_AUDIOBTN  = 2
    CLICK_MARK      = 3
   #****************************************************************
   #
   #****************************************************************
    def __init__(self, parent):
        QStyledItemDelegate.__init__(self, parent)

        imgDir = modulesDir() + "/images/"
        self._noCoverImg = QImage(imgDir + "nocover.png").scaledToHeight(self.IMG_HEIGHT, Qt.SmoothTransformation)
        self._trackBtnPix = QPixmap(imgDir + "trackBtn.png")
        self._audioBtnPix = QPixmap(imgDir + "audioBtn.png")
        self._warnPix = pyqticonloader.icon(['dialog-warning', 'messagebox_warning'], self.MARK_HEIGHT).pixmap(self.MARK_HEIGHT, self.MARK_HEIGHT)
        self._okPix = pyqticonloader.icon(['dialog-ok', 'button_ok'], self.LINE_MARK_HEIGHT).pixmap(self.LINE_MARK_HEIGHT, self.LINE_MARK_HEIGHT)
        self._errorPix = pyqticonloader.icon(['dialog-cancel', 'button_cancel'], self.LINE_MARK_HEIGHT).pixmap(self.LINE_MARK_HEIGHT, self.LINE_MARK_HEIGHT)

        self._titleFont = None
        self._filesFont = None
        self._cacheData = {}


   #****************************************************************
   #
   #****************************************************************
    def cache(self, index):
        try:
            return self._cacheData[index.internalPointer()]
        except KeyError:
            data = {}
            self._cacheData[index.internalPointer()] = data
            return data


   #****************************************************************
   #
   #****************************************************************
    def paint(self, painter, option, index):
        if isinstance(index.internalPointer(), Disc):
            if index.column() == 0:
                self._paintDisc(painter, option, index)
        else:
            self._paintTrack(painter, option, index)


   #****************************************************************
   #
   #****************************************************************
    def _paintTrack(self, painter, option, index):
        QStyledItemDelegate.paint(self, painter, option, index)

        icon = None
        status = index.model().data(index, TrackViewModel.StatusRole)
        if   status == Statuses.NotRunning: s = QString("")
        elif status == Statuses.Canceled:   s = QString("")
        elif status == Statuses.Error:      s = self.tr("Error");      icon = self._errorPix
        elif status == Statuses.Aborted:    s = self.tr("Aborted");
        elif status == Statuses.OK:         s = self.tr("OK");          icon = self._okPix
        elif status == Statuses.Extracting: s = self.tr("Extracting")
        elif status == Statuses.Encoding:   s = self.tr("Encoding")
        elif status == Statuses.Queued:     s = self.tr("Queued")
        elif status == Statuses.CalcGain:   s = self.tr("Calculate gain")
        elif status == Statuses.WaitGain:   s = self.tr("Wait gain")
        elif status == Statuses.WriteGain:  s = self.tr("Write gain")
        else: s = QString("Unknown")

        progress = index.model().data(index, TrackViewModel.StatusPercentRole)

        painter.save()
        painter.translate(option.rect.left() + 30, option.rect.top())
        windowRect = QRect(0,0, option.rect.width() - 31, option.rect.height())
        painter.setClipRect(windowRect)

        if (progress > -1):
            opt = QtGui.QStyleOptionProgressBar()
            opt.rect = windowRect.adjusted(4, 3, -4, -3)
            opt.minimum = 0
            opt.maximum = 100
            opt.progress = progress
            opt.text = QString("%1 %2%").arg(s).arg(opt.progress)

            QtGui.QApplication.style().drawControl(QtGui.QStyle.CE_ProgressBarContents, opt, painter, None)
            QtGui.QApplication.style().drawControl(QtGui.QStyle.CE_ProgressBarLabel, opt, painter, None)
        else:
            if icon:
                textWidth = painter.fontMetrics().width(s)
                imgLeft = (windowRect.width() - self.LINE_MARK_HEIGHT - 4 - textWidth) / 2
                painter.drawPixmap(imgLeft, (windowRect.height() - self.LINE_MARK_HEIGHT) / 2, icon)

                textRect = QRect(QPoint(imgLeft + self.LINE_MARK_HEIGHT + 4, 0), windowRect.bottomRight())
                painter.drawText(textRect, Qt.AlignLeft | Qt.AlignVCenter, s)
            else:
                painter.drawText(windowRect, Qt.AlignCenter | Qt.AlignVCenter, s)

        painter.restore()


   #****************************************************************
   #
   #****************************************************************
    def _paintDisc(self, painter, option, index):
        if option.state & QStyle.State_Selected:
            QStyledItemDelegate.paint(self, painter, option, index)
        else:
            painter.fillRect(option.rect, self.parent().palette().base().color())

        cache = self.cache(index)
        painter.save()

        if not self._titleFont:
            self._initFonts(painter)

        topPadding = self.TOP_PADDING if index.row() else 0;

        painter.translate(option.rect.topLeft())

        windowRect = QRect(self.MARGIN + self.MARGIN,
                           self.MARGIN + topPadding,
                           option.rect.right() - 2 * self.MARGIN,
                           option.rect.height() - 2 * self.MARGIN - topPadding - self.BOTTOM_PADDING
                          )

        # Draw cover image ................................
        img = self._noCoverImg
        imgRect = QRect(windowRect.topLeft(), img.size())
        #painter.fillRect(imgRect, self.parent().palette().base().color())
        painter.fillRect(imgRect, Qt.white)
        painter.drawImage(imgRect, img)

        # Rectangle for text drawing ......................
        textRect = QRect(windowRect)
        textRect.setLeft(imgRect.right() + self.MARGIN)

        # Draw album & artist .............................
        painter.setFont(self._titleFont)
        album = index.sibling(index.row(), TrackView.COLUMN_ALBUM).data().toString()
        artist = index.sibling(index.row(), TrackView.COLUMN_ARTIST).data().toString()
        painter.drawText(textRect, Qt.AlignLeft, "%s / %s" % (artist, album))

        # Draw audio filename .............................
        disc = index.internalPointer()
        painter.setFont(self._filesFont)
        th = painter.fontMetrics().height()
        tTop = windowRect.bottom() - 2 * th
        aTop = windowRect.bottom() - th + 2

        # Draw labels ........
        tLabelRect = QRect(textRect.left(), tTop, windowRect.width(), th)
        aLabelRect = QRect(textRect.left(), aTop, windowRect.width(), th)

        tLabelRect = self._drawLabel(self.tr("Tracks:"), tLabelRect, painter)
        aLabelRect = self._drawLabel(self.tr("Audio:"),  aLabelRect, painter)

        # Draw filenames .....
        l = max(tLabelRect.right(), aLabelRect.right()) + 2
        tFileRect = QRect(l, tTop, windowRect.width(), th)
        aFileRect = QRect(l, aTop, windowRect.width(), th)

        tFileRect = self._drawFile(disc.tracks().title(), tFileRect, painter)
        fi = QFileInfo(disc.audioFileName())
        aFileRect = self._drawFile(fi.fileName(), aFileRect, painter)

        # Draw buttons ......
        l = max(tFileRect.right(), aFileRect.right()) + 6

        painter.drawPixmap(l, tTop, self._trackBtnPix)
        painter.drawPixmap(l, aTop, self._audioBtnPix)

        cache['TRACKBTN_RECT'] = QRect(l - 4, tTop - 4, self._trackBtnPix.width() + 8, self._trackBtnPix.height() + 4)
        cache['AUDIOBTN_RECT'] = QRect(l - 4, aTop - 2, self._audioBtnPix.width() + 8, self._audioBtnPix.height() + 6)


        # Draw bottom line ................................
        painter.setPen(self.parent().palette().dark().color())
        y = option.rect.height() - self.BOTTOM_PADDING - 2
        painter.drawLine(self.MARGIN, y, windowRect.right(), y)

        # Draw warning mark ...............................
        markRect = QRect(imgRect.right() - self.MARK_HEIGHT, imgRect.bottom() - self.MARK_HEIGHT, self.MARK_HEIGHT, self.MARK_HEIGHT);
        cache['MARK_RECT'] = QRect(markRect)
        self._drawMark(index, markRect, painter)

        painter.restore()


   #****************************************************************
   #
   #****************************************************************
    def _initFonts(self, painter):
        self._titleFont = QFont(painter.font())
        self._titleFont.setPointSize(self._titleFont.pointSize() + 1)
        self._titleFont.setBold(True)

        self._filesFont = QFont(painter.font())


   #****************************************************************
   #
   #****************************************************************
    def _drawLabel(self, text, rect, painter):
        painter.save()
        painter.setPen(self.parent().palette().dark().color())
        res = painter.drawText(rect, Qt.AlignLeft | Qt.AlignVCenter, text)
        painter.restore()
        return res


   #****************************************************************
   #
   #****************************************************************
    def _drawFile(self, text, rect, painter):
        painter.save()
        res = painter.drawText(rect, Qt.AlignLeft | Qt.AlignVCenter, text)
        painter.restore()
        return res


   #****************************************************************
   #
   #****************************************************************
    def _drawMark(self, index, rect, painter):
        painter.save()
        painter.translate(rect.center())

        mRect = QRect(-self.MARK_HEIGHT/2, -self.MARK_HEIGHT/2, self.MARK_HEIGHT, self.MARK_HEIGHT)

        downPix = QPixmap(index.data(TrackViewModel.DownloadPxmapRole))
        if not downPix.isNull():
            painter.drawPixmap(mRect, downPix)

        elif not index.internalPointer().canConvert():
            painter.drawPixmap(mRect, self._warnPix)

        painter.restore()


   #****************************************************************
   #
   #****************************************************************
    def sizeHint(self, option, index ):
        res = QStyledItemDelegate.sizeHint(self, option, index)

        if not index.parent().isValid():
            height = self.IMG_HEIGHT + 2 * self.MARGIN + self.BOTTOM_PADDING # For Line
            if index.row():
                height += self.TOP_PADDING
            res.setHeight(height)
        else:
            height = res.height() + 8
            res.setHeight(height)

        return res


   #****************************************************************
   #
   #****************************************************************
    def clickType(self, index, mousePos):
        cache = self.cache(index)
        if cache:
            try:
                if cache['AUDIOBTN_RECT'].contains(mousePos):  return self.CLICK_AUDIOBTN
                if cache['TRACKBTN_RECT'].contains(mousePos):  return self.CLICK_TRACKBTN
                if cache['MARK_RECT'].contains(mousePos):      return self.CLICK_MARK

            except KeyError:
                pass

        return self.CLICK_NONE


   #****************************************************************
   #
   #****************************************************************
    def editorEvent(self, event, model, option, index):

        if not isinstance(index.internalPointer(), Disc):
            return False

        try:
            evType = event.type()

            # Drop events for link rects ..................
            if evType ==  QEvent.MouseButtonPress:
                m = event.pos() - option.rect.topLeft()
                return self.clickType(index, m)

            # Mouse click on links ........................
            if evType ==  QEvent.MouseButtonRelease:
                m = event.pos() - option.rect.topLeft()

                if event.button() == Qt.LeftButton:
                    clickType = self.clickType(index, m)

                    if clickType == self.CLICK_AUDIOBTN:
                        self.emit(SIGNAL("audioBtnClicked(QModelIndex)"), index)
                        return True

                    if clickType == self.CLICK_TRACKBTN:
                        self.emit(SIGNAL("trackBtnClicked(QModelIndex)"), index)
                        return True

                    if clickType == self.CLICK_MARK:
                        self.emit(SIGNAL("markClicked(QModelIndex)"), index)
                        return True

        except KeyError:
            return False;

        return False;



class TrackView(QTreeView):

    COLUMN_TRACKNUM = 0
    COLUMN_TITLE    = 1
    COLUMN_ARTIST   = 2
    COLUMN_ALBUM    = 3
    COLUMN_COMMENT  = 4
    COLUMN_FILENAME = 5
    COLUMNS_COUNT   = 6

   #****************************************************************
   #
   #****************************************************************
    def __init__(self, parent):
        QTreeView.__init__(self, parent)

        self._delegate = TrackViewDelegate(self)
        self.setItemDelegateForColumn(0, self._delegate)
        self. connect(self._delegate, SIGNAL("audioBtnClicked(QModelIndex)"), self._audioBtnClicked)
        self. connect(self._delegate, SIGNAL("trackBtnClicked(QModelIndex)"), self._showTrackMenu)

        model = TrackViewModel(self)
        self.setModel(model)

        self._selectionModel = TrackViewSelectionModel(model)
        self.setSelectionModel(self._selectionModel)

        #self.setMouseTracking(True)
        self.setUniformRowHeights(False)

        # Context menu ....................................
        self.setContextMenuPolicy(Qt.DefaultContextMenu)

        header = self.header()
        header.setContextMenuPolicy(Qt.CustomContextMenu)
        self.connect(header, SIGNAL("customContextMenuRequested(QPoint)"), self._headerContextMennu)



   #****************************************************************
   #
   #****************************************************************
    def _audioBtnClicked(self, index):
        self.emit(SIGNAL('selectAudioFile(int)'), index.row())


   #****************************************************************
   #
   #****************************************************************
    def project(self):
        return self.model().project()


   #****************************************************************
   #
   #****************************************************************
    def setProject(self, project):
        self.connect(project, SIGNAL("layoutChanged()"), self._layoutChanged)

        self.model().setProject(project)
        self._layoutChanged()


   #****************************************************************
   #
   #****************************************************************
    def _layoutChanged(self):
        for i in xrange(0, self.model().project().count()):
            self.setFirstColumnSpanned(i, QModelIndex(), True)

        self.expandAll()


   #****************************************************************
   # Open treeview inline editor
   #****************************************************************
    def openEditor(self, column):
        index = QModelIndex(self.currentIndex())

        if not index.parent().isValid():
            index = index.child(0, 0)
            self.selectionModel().setCurrentIndex(index, QItemSelectionModel.NoUpdate)

        if (index.isValid()):
            index = index.sibling(index.row(), column)
            self.edit(index)


   #****************************************************************
   #
   #****************************************************************
    def selectedTracks(self):
        res = []
        idxs = self.selectionModel().selectedIndexes()
        for index in idxs:
            track = index.internalPointer()
            if isinstance(track, Track):
                res.append(track)

        return list(set(res))


   #****************************************************************
   #
   #****************************************************************
    def selectedDiscs(self):
        res = []
        idxs = self.selectionModel().selectedIndexes()

        for index in idxs:
            obj = index.internalPointer()
            if isinstance(obj, Track):  res.append(obj.trackSet().disc())
            if isinstance(obj, Disc):   res.append(obj)

        return list(set(res))


   #****************************************************************
   #
   #****************************************************************
    def _headerContextMennu(self, pos):
        menu = QMenu()
        model = self.model()

        for i in xrange(1, model.columnCount(QModelIndex())):
            title = model.headerData(i, Qt.Horizontal, Qt.DisplayRole).toString()
            act = QtGui.QAction(title, menu)
            act.setCheckable(True)
            act.setChecked(not self.isColumnHidden(i))
            self.connect(act, SIGNAL('triggered(bool)'), (lambda show, column=i: self.setColumnHidden(column, not show)))
            menu.addAction(act)

        menu.exec_(self.mapToGlobal(pos))


   #****************************************************************
   #
   #****************************************************************
    def _showTrackMenu(self, index):
        disc = index.internalPointer()
        menu = QMenu()

        for ts in disc.trackSets():
            act = QtGui.QAction(ts.title(), menu)
            act.setCheckable(True)
            act.setChecked(ts.uri() == disc.tracks().uri())
            self.connect(act, SIGNAL('triggered()'), ts.activate)
            menu.addAction(act)

        menu.addSeparator()

        act = QtGui.QAction(pyqticonloader.icon(['document-open', 'fileopen']), self.tr('Select another CUE file'), menu)
        self.connect(act, SIGNAL('triggered()'),  (lambda: self.emit(SIGNAL('selectCueFile(int)'), index.row())))
        menu.addAction(act)

        act = QtGui.QAction(pyqticonloader.icon(['download', 'web-browser', 'network']), self.tr('Get data from CDDB'), menu)
        self.connect(act, SIGNAL('triggered()'),  disc.downloadInfo)
        act.setEnabled(disc.canDownloadInfo())
        menu.addAction(act)

        try:
            vpPos = self.viewport().pos() + self.visualRect(index).topLeft()
            rect = self._delegate.cache(index)['TRACKBTN_RECT']
            p = rect.bottomLeft() + vpPos + QPoint(0, 2)
            menu.exec_(self.mapToGlobal(p))
        except KeyError:
            pass


   #****************************************************************
   #
   #****************************************************************
    def contextMenuEvent(self, event):
        event.ignore()

        index = self.indexAt(event.pos())
        if not index.isValid():
            return

        if isinstance(index.internalPointer(), Track):
            disc = index.internalPointer().trackSet().disc()
            discIndex = index.parent()
        else:
            disc = index.internalPointer()
            discIndex = index

        menu = QMenu()
        editMenu = QMenu(self.tr('Edit'), menu)
        model = self.model()
        for i in xrange(0, model.columnCount(QModelIndex())):
            if int(model.flags(model.index(0, i, discIndex)) & Qt.ItemIsEditable):
                title = model.headerData(i, Qt.Horizontal, Qt.DisplayRole).toString()
                act = QtGui.QAction(title, menu)
                self.connect(act, SIGNAL('triggered()'), (lambda column=i: self.openEditor(column)))
                editMenu.addAction(act)

        menu.addMenu(editMenu)
        menu.addSeparator()


        act = QtGui.QAction(pyqticonloader.icon(['document-open', 'fileopen']), self.tr('Select another audio file'), menu)
        self.connect(act, SIGNAL('triggered()'),  (lambda: self.emit(SIGNAL('selectAudioFile(int)'), discIndex.row())))
        menu.addAction(act)

        act = QtGui.QAction(pyqticonloader.icon(['document-open', 'fileopen']), self.tr('Select another CUE file'), menu)
        self.connect(act, SIGNAL('triggered()'),  (lambda: self.emit(SIGNAL('selectCueFile(int)'), discIndex.row())))
        menu.addAction(act)

        act = QtGui.QAction(pyqticonloader.icon(['download', 'web-browser', 'network']), self.tr('Get data from CDDB'), menu)
        self.connect(act, SIGNAL('triggered()'),  disc.downloadInfo)
        act.setEnabled(disc.canDownloadInfo())
        menu.addAction(act)

        menu.exec_(event.globalPos())


class TrackViewSelectionModel(QItemSelectionModel):
   #****************************************************************
   #
   #****************************************************************
    def __init__(self, model, parent = None):
        QItemSelectionModel.__init__(self, model, parent)


   #****************************************************************
   #
   #****************************************************************
    def select(self, selection, command):
        if isinstance(selection, QItemSelection):
            newSelection = selection
        else:
            newSelection = QItemSelection(selection, selection)


        idxs = newSelection.indexes()
        for index in idxs:
            if index.parent().isValid():# or index.column() != 0:
                continue

            model = self.model()
            index1 = index.child(0, 0)
            index2 = index.child(model.rowCount(index)-1, model.columnCount(index)-1)
            newSelection.select(index1, index2)

        QItemSelectionModel.select(self, newSelection, command)



class TrackViewModel(QAbstractItemModel):
    StatusPercentRole = Qt.UserRole
    StatusRole = StatusPercentRole + 1
    DownloadPxmapRole = StatusRole + 1

   #****************************************************************
   #
   #****************************************************************
    def __init__(self, view=None):
        QAbstractItemModel.__init__(self, view)
        self._project = None
        self._view = view

        self._downloadsStates={}
        self._downloadMovie = QMovie(modulesDir() + "/images/wait.mng")
        self.connect(self._downloadMovie, SIGNAL("updated(QRect)"), self._movieUpdated)


   #****************************************************************
   #
   #****************************************************************
    def refresh(self):
        self.emit(SIGNAL("layoutChanged()"))


   #****************************************************************
   #
   #****************************************************************
    def project(self):
        return self._project


   #****************************************************************
   #
   #****************************************************************
    def setProject(self, project):
        self._project = project
        self.connect(project, SIGNAL("trackDataChanged(int,int)"), self._trackDataChanged)
        self.connect(project, SIGNAL("layoutChanged()"),  SIGNAL("layoutChanged()"))
        self.connect(project, SIGNAL("downloadStarted(int)"), self._downloadStarted)
        self.connect(project, SIGNAL("downloadFinished(int)"), self._downloadFinished)
        self.connect(project, SIGNAL("beginRemoveDisc(int)"), (lambda n: self.beginRemoveRows(QModelIndex(), n, n)))
        self.connect(project, SIGNAL("endRemoveDisc()"), self.endRemoveRows)


   #****************************************************************
   #
   #****************************************************************
    def _trackDataChanged(self, discNum, trackNum):
        discIndex = self.index(discNum, 0, QModelIndex())
        index1 = self.index(trackNum, 0, discIndex)
        index2 = self.index(trackNum, TrackView.COLUMNS_COUNT-1, discIndex)
        self.emit(SIGNAL("dataChanged(QModelIndex,QModelIndex)"), index1, index2)


   #****************************************************************
   #
   #****************************************************************
    def progressChanged(self, discNum, trackNum):
        discIndex = self.index(discNum, 0, QModelIndex())
        index = self.index(trackNum, 0, discIndex)
        self.emit(SIGNAL("dataChanged(QModelIndex,QModelIndex)"), index, index)


   #****************************************************************
   #
   #****************************************************************
    def discDataChanged(self, discNum):
        index = self.index(discNum, 0, QModelIndex())
        self.emit(SIGNAL("dataChanged(QModelIndex,QModelIndex)"), index, index)


   #****************************************************************
   #
   #****************************************************************
    def columnCount(self, parent):
        return TrackView.COLUMNS_COUNT


   #****************************************************************
   #
   #****************************************************************
    def rowCount(self, parent):
        if not self._project:
            return 0

        if not parent.isValid():
            return  self._project.count()

        if isinstance(parent.internalPointer(), Disc):
            disc = parent.internalPointer()
            return disc.tracks().count()

        return 0


   #****************************************************************
   #
   #****************************************************************
    def headerData(self, section, orientation, role):
        if orientation == Qt.Horizontal and role == Qt.DisplayRole:
            if (section == TrackView.COLUMN_TRACKNUM): return QVariant(self.tr("Track",   "Table header."))
            if (section == TrackView.COLUMN_TITLE):    return QVariant(self.tr("Title",   "Table header."))
            if (section == TrackView.COLUMN_ARTIST):   return QVariant(self.tr("Artist",  "Table header."))
            if (section == TrackView.COLUMN_ALBUM):    return QVariant(self.tr("Album",   "Table header."))
            if (section == TrackView.COLUMN_COMMENT):  return QVariant(self.tr("Comment", "Table header."))
            if (section == TrackView.COLUMN_FILENAME): return QVariant(self.tr("File",    "Table header."))

        return QVariant()


   #****************************************************************
   #
   #****************************************************************
    def index(self, row, column, parent):
        if not self._project:
            return QModelIndex()

        if not self.hasIndex(row, column, parent):
            return QModelIndex()

        obj = parent.internalPointer()

        if isinstance(obj, Disc):
            return self.createIndex(row, column, obj.tracks()[row])
        else:
            return self.createIndex(row, column, self._project[row])

        return QModelIndex()


   #****************************************************************
   #
   #****************************************************************
    def parent(self, index):
        if not index.isValid():
            return QModelIndex()

        obj = index.internalPointer()
        if isinstance(obj, Track):
            disc = obj.trackSet().disc()
            row = self._project.indexOf(disc)
            return self.index(row, 0, QModelIndex())

        return QModelIndex()


   #****************************************************************
   #
   #****************************************************************
    def flags(self, index):
        if not index.isValid():
            return Qt.ItemIsEnabled

        res = Qt.ItemIsEnabled | Qt.ItemIsSelectable
        if (isinstance(index.internalPointer(), Track) and
            (index.column() == TrackView.COLUMN_TITLE  or
             index.column() == TrackView.COLUMN_ARTIST or
             index.column() == TrackView.COLUMN_ALBUM  or
             index.column() == TrackView.COLUMN_COMMENT
            )
           ):
            res = res | Qt.ItemIsEditable

        return res


   #****************************************************************
   #
   #****************************************************************
    def data(self, index, role):
        if not index.isValid():
            return QVariant()

        obj = index.internalPointer()

        if isinstance(obj, Disc):    return self._discData(obj, index, role)
        elif isinstance(obj, Track): return self._trackData(obj, index, role)
        return QVariant()


   #****************************************************************
   #
   #****************************************************************
    def _discData(self, disc, index, role):

        # Display & Edit :::::::::::::::::::::::::::::::::::
        if (role == Qt.DisplayRole or role == Qt.EditRole ):
            tracks = disc.tracks()

            if tracks.count() == 0:
                return QVariant()

            tagName = ''
            if   index.column() == TrackView.COLUMN_TITLE:   tagName = 'TITLE'
            elif index.column() == TrackView.COLUMN_ARTIST:  tagName = 'PERFORMER'
            elif index.column() == TrackView.COLUMN_ALBUM:   tagName = 'ALBUM'
            else: return QVariant()

            s = tracks[0].tag(tagName)
            for track in tracks:
                if s != track.tag(tagName):
                    return QVariant(self.tr('Multiple values'))

            return QVariant(s)

        # ToolTip ::::::::::::::::::::::::::::::::::::::::::
        elif (role == Qt.ToolTipRole):
            s = disc.canConvertString()
            if s:
                return QVariant(self.tr("Conversion is not possible.\n%1").arg(s))
            else:
                return QVariant()

        # StatusPercent ::::::::::::::::::::::::::::::::::::
        elif (role == self.StatusPercentRole):
            try:
                return QVariant(self._downloadsStates[index.row()])
            except KeyError:
                return QVariant()
                
        # Download pixmap ::::::::::::::::::::::::::::::::::
        elif role == self.DownloadPxmapRole:
            try:
                self._downloadsStates[index.row()]
                return QVariant(self._downloadMovie.currentPixmap())
            except KeyError:
                return QVariant()


        return QVariant()


   #****************************************************************
   #
   #****************************************************************
    def _trackData(self, track, index, role):

        # Display & Edit :::::::::::::::::::::::::::::::::::
        if (role == Qt.DisplayRole or role == Qt.EditRole ):
            if   index.column() == TrackView.COLUMN_TRACKNUM: return QVariant("%02d" % track.trackNum())
            elif index.column() == TrackView.COLUMN_TITLE:    return QVariant(track.title())
            elif index.column() == TrackView.COLUMN_ARTIST:   return QVariant(track.artist())
            elif index.column() == TrackView.COLUMN_ALBUM:    return QVariant(track.album())
            elif index.column() == TrackView.COLUMN_COMMENT:  return QVariant(track.tag('COMMENT'))
            elif index.column() == TrackView.COLUMN_FILENAME: return QVariant(track.resultFileName())

        # ToolTip ::::::::::::::::::::::::::::::::::::::::::
        if (role == Qt.ToolTipRole):
            if index.column() == TrackView.COLUMN_FILENAME: return QVariant(track.resultFilePath())

        # StatusPercent ::::::::::::::::::::::::::::::::::::
        elif (role == self.StatusPercentRole):
            return track.progress()

        # Status :::::::::::::::::::::::::::::::::::::::::::
        elif (role == self.StatusRole):
            return track.status()

        return QVariant()


   #****************************************************************
   #
   #****************************************************************
    def setData(self, index, value, role):
        if not index.isValid():
            return False

        if role != Qt.EditRole:
            return False

        tracks = self._view.selectedTracks()
        for track in tracks:
            if (index.column() == TrackView.COLUMN_TITLE):     track.setTitle(value.toString())
            elif (index.column() == TrackView.COLUMN_ARTIST):  track.setArtist(value.toString())
            elif (index.column() == TrackView.COLUMN_ALBUM):   track.setAlbum(value.toString())
            elif (index.column() == TrackView.COLUMN_COMMENT): track.setTag('COMMENT', value.toString())

        return True


   #****************************************************************
   #
   #****************************************************************
    def _downloadStarted(self, discNum):
        self._downloadsStates[discNum] = True
        if self._downloadMovie.state() != QMovie.Running:
            self._downloadMovie.start()


   #****************************************************************
   #
   #****************************************************************
    def _downloadFinished(self, discNum):
        try:
            del self._downloadsStates[discNum]
        except KeyError:
            pass

        if not len(self._downloadsStates):
            self._downloadMovie.stop()

        index = self.index(discNum, 0, QModelIndex())
        self.emit(SIGNAL("dataChanged(QModelIndex,QModelIndex)"), index, index)


   #****************************************************************
   #
   #****************************************************************
    def _movieUpdated(self):
        for n in self._downloadsStates:
            index = self.index(n, 0, QModelIndex())
            self.emit(SIGNAL("dataChanged(QModelIndex,QModelIndex)"), index, index)
            self._downloadsStates[n] += 45

